﻿using System;
using System.Collections.Generic;
using ParserGenerator.Exceptions;
using ParserGenerator.Extensions;
using ParserGenerator.GrammarConstruction;
using ParserGenerator.ParserConstruction;
using ParserGenerator.ScannerConstruction;

namespace ParserGenerator.GrammarAnalysis
{
    [Serializable]
    internal sealed class LexicalPrecedenceResolutionCache
    {
        private readonly ScannerAutomaton _scannerAutomaton;
        private readonly Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, bool> _conflicted;
        private readonly Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedenceResolution> _cache;

        internal LexicalPrecedenceResolutionCache(ScannerAutomaton scannerAutomaton)
        {
            if (scannerAutomaton == null)
                throw new ArgumentNullException(nameof(scannerAutomaton));
            if (scannerAutomaton.Starts.Count > 1)
                throw new GrammarErrorException(SR.PSLRCanNotUseExtraStarts);
            var e1 = new GrammarErrorException(SR.PSLRCanNotUseBOLNorEOLOps);
            var e2 = new GrammarErrorException(SR.PSLRMustYieldTokens);
            var reductions = scannerAutomaton.States.Reductions;
            foreach (var t in reductions)
            {
                if (t.Key.AtLineBegin || t.Key.AtLineEnd)
                    e1.AddLocation(t.Key.Context);
                if (scannerAutomaton.Tokens.TryGetToken(t.Key, false) == null)
                    e2.AddLocation(t.Key.Context);
            }
            if (e1.Locations.Count > 0)
                throw e1;
            if (e2.Locations.Count > 0)
                throw e2;
            _scannerAutomaton = scannerAutomaton;
            _conflicted = new Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, bool>();
            _cache = new Dictionary<KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>, LexicalPrecedenceResolution>();
        }

        private void Resolve(GrammarTerminalSymbol x, GrammarTerminalSymbol y)
        {
            if (x == null)
                throw new ArgumentNullException(nameof(x));
            if (y == null)
                throw new ArgumentNullException(nameof(y));
            HashSet<GrammarTerminalSymbol> tokens = new HashSet<GrammarTerminalSymbol>() { x, y };
            LexicalPrecedenceResolver resolver = new LexicalPrecedenceResolver(_scannerAutomaton, tokens);
            resolver.Resolve();
            var item = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(x, y);
            _conflicted[item] = resolver.Conflicts.Count > 0;
            _cache[item] = new LexicalPrecedenceResolution(resolver.ScannerAccept);
        }

        private bool HaveConflicts(GrammarTerminalSymbol x, GrammarTerminalSymbol y)
        {
            if (x == null)
                throw new ArgumentNullException(nameof(x));
            if (y == null)
                throw new ArgumentNullException(nameof(y));
            if (x.Number > y.Number)
            {
                GrammarTerminalSymbol z = x;
                x = y;
                y = z;
            }
            var item = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(x, y);
            bool value;
            if (!_conflicted.TryGetValue(item, out value))
            {
                Resolve(x, y);
                value = _conflicted[item];
            }
            return value;
        }

        private LexicalPrecedenceResolution GetResolution(GrammarTerminalSymbol x, GrammarTerminalSymbol y)
        {
            if (x == null)
                throw new ArgumentNullException(nameof(x));
            if (y == null)
                throw new ArgumentNullException(nameof(y));
            if (x.Number > y.Number)
            {
                GrammarTerminalSymbol z = x;
                x = y;
                y = z;
            }
            var item = new KeyValuePair<GrammarTerminalSymbol, GrammarTerminalSymbol>(x, y);
            LexicalPrecedenceResolution resolution;
            if (!_cache.TryGetValue(item, out resolution))
            {
                Resolve(x, y);
                resolution = _cache[item];
            }
            return resolution;
        }

        internal void ExtendConflictSet(HashSet<GrammarTerminalSymbol> set, HashSet<GrammarTerminalSymbol> used)
        {
            if (set == null)
                throw new ArgumentNullException(nameof(set));
            if (used == null)
                throw new ArgumentNullException(nameof(used));
            if (used.Count <= 1)
                return;
            var list = new List<GrammarTerminalSymbol>(used);
            foreach (var t in list.EnumPairs())
            {
                if (HaveConflicts(t.Key, t.Value))
                {
                    set.Add(t.Key);
                    set.Add(t.Value);
                }
            }
        }

        internal bool IsCompatible(ParserAutomatonItemLookaheadSets sets1, ParserAutomatonItemLookaheadSets sets2)
        {
            if (sets1 == null)
                throw new ArgumentNullException(nameof(sets1));
            if (sets2 == null)
                throw new ArgumentNullException(nameof(sets2));
            HashSet<GrammarTerminalSymbol> set1 = sets1.GetAllTokens();
            HashSet<GrammarTerminalSymbol> set2 = sets2.GetAllTokens();
            if (set1.SetEquals(set2))
                return true;
            foreach (GrammarTerminalSymbol x in set1)
                foreach (GrammarTerminalSymbol y in set2)
                    if (x != y && HaveConflicts(x, y))
                        return false;
            return true;
        }
    }
}
